package com.zerowidth.moc.web.filter;

import com.alibaba.fastjson.JSONObject;
import com.zerowidth.db.op.LogDB;
import com.zerowidth.db.op.SignDB;
import com.zerowidth.moc.web.resp.ResultBean;
import com.zerowidth.moc.web.utils.GsonUtils;
import com.zerowidth.moc.web.utils.HttpUtils;
import com.zerowidth.moc.web.utils.LogUtils;
import com.zerowidth.moc.web.utils.SignUtils;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;

/**
 * 签名检验、防重放攻击
 */
@Component
public class SignAuthAntiReplayFilter implements Filter {

    private static final List<String> escapeUrlList = new ArrayList<String>() {
        {
            add("/favicon.ico");
            add("/test");
            add("/api/v1/ip_test_no_sign");
            add("/api/v1/version");
            add("/api/v1/pay_notify/ali");
            add("/api/v1/pay_notify/wx");
            add("/api/v1/pay_notify/android");
        }
    };

    /**
     * 将 xxx.war 文件包部署到 tomcat8 后，因为 xxx.war 文件本身会被解压到 xxx 文件夹中，
     * 因此网络请求路径中会多 xxx 这一部分，xxx 这一部分不受代码控制
     *
     * 所以此方法只能用 "以某某为结尾" 来判断
     * @param uri
     */
    private boolean isEscapeUrl(String uri) {
        for (int i = 0; i < escapeUrlList.size(); i++) {
            if (uri.endsWith(escapeUrlList.get(i))){
                return true;
            }
        }
        return false;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        LogUtils.warn("SignAuthFilter.init");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        LogUtils.warn("SignAuthFilter.doFilter");

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        String uri = request.getRequestURI();

        // todo
        //LogDB.insert("uri = " + uri);

        if (isEscapeUrl(uri)) {
            //LogDB.insert("escapeUrl = " + uri);
            chain.doFilter(request, response);
        }  else {

            // 防止流读取一次后就没有了, 所以需要将流继续写出去
            HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);

            //获取全部参数(包括URL和body上的)
            SortedMap<String, String> allParams = HttpUtils.getAllParams(requestWrapper);

            if (SignUtils.isEscapeSign(allParams)) {
                //跳过签名校验，也就不存在判断重放的逻辑
                chain.doFilter(requestWrapper, response);
            } else {
                String url = requestWrapper.getRequestURL().toString();
                LogUtils.warn("接收到请求:" + url);
                LogUtils.warn("接收到参数:" + GsonUtils.toJsonString(allParams));
                String generateSignValue = SignUtils.generateSign(url, allParams);
                LogUtils.warn("服务器计算的校验签名:" + generateSignValue);

                boolean isSignValid = SignUtils.verifySign(allParams, generateSignValue);
                if (isSignValid) {
                    //签名正确
                    chain.doFilter(requestWrapper, response);

                    //暂时不判断、也不记录每个请求的签名，因为服务器的性能不能记录如此多的数据
//                    if (SignDB.checkExist(generateSignValue)) {
//                        //已经处理过此签名的请求，则认为是 重放攻击
//                        writeErrorResponse(response, -101, "禁止重放");
//                    } else {
//                        //正常的请求，需要处理，并记录此次签名
//                        SignDB.insertSign(generateSignValue, url);
//                        chain.doFilter(requestWrapper, response);
//                    }
                } else {
                    //签名错误
                    //校验失败返回前端
                    writeErrorResponse(response, -100, "请求签名错误");
                }
            }
        }
    }

    private void writeErrorResponse(HttpServletResponse response, int code, String errorMsg) throws IOException {
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        response.setContentType("application/json; charset=utf-8");

        ResultBean<Object> singFailBean = new ResultBean<>();
        singFailBean.code = code;
        singFailBean.message = errorMsg;

        PrintWriter out = response.getWriter();
        out.append(JSONObject.toJSONString(singFailBean));
    }

    @Override
    public void destroy() {
        LogUtils.warn("SignAuthFilter.destroy");
    }

}
